大家好,歡迎來到第二十六天!在 Day 25,我們經歷了一場真正的「iOS 部署風暴」:
Apple Push Services
不是 Apple Distribution
0 valid identities found
讓我們困惑了數小時Missing password
在 CI/CD 環境中無解Version code 21 has already been used
那種「每修好一個 bug,又跳出兩個新錯誤」的感覺,相信每個開發者都深有體會。我們在 Keychain Access 和 Apple Developer Portal 之間來回切換,在終端機和 GitHub Actions 日誌中尋找線索,甚至開始懷疑是不是哪個環節的理解出了問題。
但今天,我們終於突破了所有障礙!經過昨天一整天的問題排查和解決,我們成功實現了 Crew Up 專案的完整 iOS 部署自動化。更重要的是,我們找到了從根本上解決這些問題的方式:從傳統的 Apple ID 認證升級到 App Store Connect API Keys,並建立了自動化版本號管理機制。
這些解決方案不僅讓我們的專案能順利部署,更讓我們對 iOS 部署流程有了更深刻的理解。
在 Day 25 中,我們遇到了以下主要問題:
經過深入研究和實戰測試,我們成功解決了所有問題,並實現了以下改進:
如果你也遇到類似的 iOS 部署問題,從我們的專案經驗來看,以下是兩個關鍵的解決方式:
1. 改用 App Store Connect API Keys
2. 自動化版本號管理
[skip ci]
標記避免觸發無限循環的 CI/CD 流程這兩個改進讓我們的部署成功率大幅提升。
在昨天的實戰中,我們發現傳統的 Apple ID + App Specific Password 認證方式存在以下問題:
傳統方式的缺點:
API Keys 的優勢:
根據我們在 Day 25 的經驗,以下是正確的設定步驟:
前往 App Store Connect:
進入 API Keys 管理:
建立新的 API Key:
GitHub Actions CI/CD
記錄重要資訊:
ABC1234DEF
12345678-1234-1234-1234-123456789abc
# 轉換 .p8 檔案為 Base64
base64 -i AuthKey_ABC1234DEF.p8 | pbcopy
# 驗證轉換結果
base64 -i AuthKey_ABC1234DEF.p8 | head -c 100
在 GitHub Repository 中設定以下 3 個 Secrets:
APP_STORE_CONNECT_API_KEY_ID
ABC1234DEF
(您的 Key ID)APP_STORE_CONNECT_ISSUER_ID
12345678-1234-1234-1234-123456789abc
(您的 Issuer ID)APP_STORE_CONNECT_API_KEY_CONTENT
修改 Fastfile:
# ios/fastlane/Fastfile
# 在上傳之前設定 API Key 認證
app_store_connect_api_key(
key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"],
issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
key_content: ENV["APP_STORE_CONNECT_API_KEY_CONTENT"],
is_key_content_base64: true
)
# 上傳到 TestFlight
upload_to_testflight(
skip_waiting_for_build_processing: true,
app_identifier: "com.yourcompany.yourapp"
)
更新 CI/CD 環境變數:
# .github/workflows/ci-cd.yml
- name: Build and Deploy iOS to TestFlight
run: |
echo "🔧 Environment variables:"
echo "APP_STORE_CONNECT_API_KEY_ID: $APP_STORE_CONNECT_API_KEY_ID"
echo "APP_STORE_CONNECT_ISSUER_ID: $APP_STORE_CONNECT_ISSUER_ID"
echo "APP_STORE_CONNECT_API_KEY_CONTENT is set: ${APP_STORE_CONNECT_API_KEY_CONTENT:+YES}"
echo ""
echo "📱 Running Fastlane beta..."
cd ios
bundle exec fastlane beta --verbose
env:
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
在昨天的實戰中,我們遇到了 Google Play Console 上傳失敗的問題:
Error: Version code 21 has already been used.
這個問題的根本原因是:
在專案中,我們使用語義化版本號格式:
# pubspec.yaml
version: MAJOR.MINOR.PATCH+BUILD_NUMBER
# 範例:version: 1.1.14+29
修改 version-management job:
# .github/workflows/ci-cd.yml
version-management:
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
version: ${{ steps.version.outputs.version }}
build-number: ${{ steps.version.outputs.build-number }}
is-release: ${{ steps.version.outputs.is-release }}
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Auto increment version for main branch
if: github.ref == 'refs/heads/main'
run: |
# 讀取當前版本
CURRENT_VERSION=$(grep 'version:' pubspec.yaml | sed 's/version: //' | cut -d'+' -f1)
CURRENT_BUILD=$(grep 'version:' pubspec.yaml | sed 's/version: //' | cut -d'+' -f2)
# 自動遞增建置編號
NEW_BUILD=$((CURRENT_BUILD + 1))
NEW_VERSION_WITH_BUILD="$CURRENT_VERSION+$NEW_BUILD"
# 更新 pubspec.yaml
sed -i "s/version: .*/version: $NEW_VERSION_WITH_BUILD/" pubspec.yaml
echo "Updated version from $CURRENT_VERSION+$CURRENT_BUILD to $NEW_VERSION_WITH_BUILD"
# 提交版本變更
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add pubspec.yaml
# 使用 [skip ci] 避免觸發新的 CI/CD 循環
git commit -m "chore: auto increment version to $NEW_VERSION_WITH_BUILD [skip ci]" || exit 0
git push
💡 關於 [skip ci]
的實用說明:
在 CI/CD 實作中,[skip ci]
是一個通用慣例,用來告訴 GitHub Actions(以及大多數 CI/CD 系統)不要因為這次由 Action 自己產生的提交而觸發新的 CI/CD 流程。
為什麼需要 [skip ci]
:
[skip ci]
,版本號更新的提交會觸發新的 CI/CD,新的 CI/CD 又會更新版本號,形成無限循環其他常見的跳過標記:
[skip ci]
- GitHub Actions、GitLab CI[ci skip]
- Travis CI、CircleCI[skip actions]
- GitHub Actions 專用***NO_CI***
- Azure Pipelines在我們的專案中,使用 [skip ci]
確保了版本號管理流程的穩定性,避免了可能的無限循環問題。
更新依賴關係:
# .github/workflows/ci-cd.yml
# 多環境 Android 建置
build-android-multi-env:
needs: [quality-and-tests, version-management]
runs-on: ubuntu-latest
# iOS 建置與 TestFlight 部署
build-and-deploy-ios:
needs: [quality-and-tests, version-management]
runs-on: macos-latest
MAJOR.MINOR.PATCH+BUILD
格式經過實戰測試,我們確立了以下分支策略:
develop 分支:
develop
分支main 分支:
main
分支graph TD
A[開發者推送程式碼] --> B{分支判斷}
B -->|develop| C[Firebase App Distribution]
B -->|main| D[版本號自動遞增]
D --> E[Android 建置]
D --> F[iOS 建置]
E --> G[Google Play Console]
F --> H[TestFlight]
G --> I[Slack 通知]
H --> I
C --> J[測試環境通知]
在今天的測試中,我們成功實現了:
從今天的 CI/CD 執行結果可以看到:
成功的 Jobs:
quality-and-tests
:程式碼品質檢查通過version-management
:版本號自動遞增成功build-android-multi-env
:Android 多環境建置成功build-and-deploy-ios
:iOS TestFlight 部署成功deploy-google-play
:Google Play Console 部署成功notify-slack
:Slack 通知發送成功正確跳過的 Jobs:
deploy-firebase-distribution
:因為推送到 main 分支而非 develop版本號管理:
Updated version from X.Y.Z+N to X.Y.Z+N+1
# 實際範例:從 1.1.14+29 自動遞增
API Key 認證:
🔍 Debug Environment Variables:
APP_STORE_CONNECT_API_KEY_ID: ABC1234DEF
APP_STORE_CONNECT_ISSUER_ID: 12345678-1234-1234-1234-123456789abc
APP_STORE_CONNECT_API_KEY_CONTENT is set: YES
部署成功:
傳統方式(已棄用):
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
新的 API Keys 方式(推薦):
env:
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}
問題:手動管理版本號容易出錯,導致重複版本號
解決:自動化版本號遞增,確保每次部署都有唯一版本號
develop 分支:用於測試環境部署
main 分支:用於生產環境部署
好處:清晰的環境分離,避免測試版本影響生產環境
版本管理 job 需要寫入權限:
version-management:
permissions:
contents: write
其他 jobs 只需要讀取權限:
build-android-multi-env:
permissions:
contents: read
actions: write
今天我們成功實現了 Crew Up 專案的完整自動化部署:
經過兩天的實戰,我們成功解決了 iOS 部署過程中的所有挑戰,並建立了穩定、可靠的自動化部署流程。這個過程讓我們深刻理解了:
經過 Day 23 到 Day 26 的完整實作,我們已經成功建立了從開發到部署的完整自動化流程:
Day 23 - CI/CD 基礎建立:
Day 24 - Android 部署實作:
Day 25 - iOS 部署挑戰:
Day 26 - iOS 部署成功:
從這四天的實作中,我們建立了:
明天(Day 27),我們將分享 Cursor + Claude 實務開發經驗,探討如何在實際專案中運用 AI 輔助開發:
在完成了完整的 CI/CD 自動化部署後,讓我們來看看如何運用 AI 工具來提升開發效率和程式碼品質!
期待與您在 Day 27 相見!